package de.gwtprime.server.model;

import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.OneToMany;
import javax.persistence.Table;

import org.apache.log4j.Logger;

import com.google.appengine.api.taskqueue.Queue;
import com.google.appengine.api.taskqueue.QueueFactory;
import com.google.appengine.api.taskqueue.TaskOptions;
import com.google.web.bindery.requestfactory.server.RequestFactoryServlet;

import de.gwtprime.server.servlets.ActivationServlet;
import de.gwtprime.server.sql.helper.HibernateHelper;
import de.gwtprime.shared.util.AppUtilsShared;


@Entity
@Table(name = "users2")
public class User2 extends EntityBase {

	
	/**
	 * Serial ID
	 */
	private static final long serialVersionUID = 1L;

	@Column(unique=true)
	private String email;

	private boolean isAdmin;
	
	private String name;
	
	private String pass1;
	
	private String activationCode;
	
	private boolean isActive = false;
	
	/**
	 * The karma can be increased by "good" actions like good questions, answers...
	 * The more karma a user has, the more privileges he gains
	 */
	private Long karma;

	/**
	 * Logger
	 */
	private static Logger logger = Logger.getLogger(User2.class);

	private Double balanceBitcoin = 0d;
	
	private Double balanceBitcoinReserved = 0d;

	private Double balanceEuro = 0d;
	
	private String bankRecipient;
	
	private String bankIBAN;
	
	private String bankBIC;
	
	private String bankName;
	
	private String privateWithdrawAddress;
	
	private Set<Transaction> transactions = new HashSet<Transaction>();
	/**
	 * Default constructor for JPA
	 */
	public User2(){}
	public User2(String name){
		this.setName(name);
	}

	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}



	public Long persist(){
		return HibernateHelper.persist(this);
	}

	public void remove(){

		HibernateHelper.remove(this);

	}

	public static User2 findUser2(Long id) {
		return HibernateHelper.find(User2.class, id);
	}
	public String getEmail() {
		return email;
	}
	public void setEmail(String email) {
		this.email = email;
	}


	/**
	 * First check in the session: If there no user is the session context yet -> check the DB.
	 * @param email
	 * @return
	 */
	public static User2 findUserByEmail(String email){

		logger.info("Getting user by email: "+email);
		
		User2 userFromSession = findUserInSession();

		if(userFromSession!=null)
			return userFromSession;

		logger.info(String.format("No user was found in the session. Looking up in the DB the user '%s'", email));
		String query = String.format("from %s where email='%s'", User2.class.getName(), email);


		User2 user = (User2)HibernateHelper.queryDB(new User2(), query);
		
		logger.info(String.format("User looked up in the DB: '%s', id: %s", user == null ? "no user found" : user.getEmail(), user == null ? "-" : user.getId()));
		
//		if(RequestFactoryServlet.getThreadLocalRequest()!=null){
//			RequestFactoryServlet.getThreadLocalRequest().getSession().setAttribute("user", user);
//		}
		return user;
	}
	
	/**
	 * Delete users with the given email
	 * @param email
	 */
	public static void deleteUsersByEmail(String email){
		
		List<User2> user = new ArrayList<>();
				
				if(email == null){
					user = HibernateHelper.queryDBForList(new User2(), 
							String.format("from %s where email IS NULL", User2.class.getSimpleName()));
				}else{
					user = HibernateHelper.queryDBForList(new User2(), 
							String.format("from %s where email='%s'", User2.class.getSimpleName(), email));
				}
		
		for(User2 u : user){
			List<Order> orders = HibernateHelper.queryDBForList(new Order(), 
					String.format("from %s where user='%s'", Order.class.getSimpleName(), u.getEmail()));
			
			for(Order o : orders){
				o.remove();
			}
			
			u.remove();
		}
	}
	public static User2 findUserInSession() {
		logger.info("Looking up the user in the session");
		logger.info("RequestFactoryServlet.getThreadLocalRequest(): " +RequestFactoryServlet.getThreadLocalRequest());
		
		if(RequestFactoryServlet.getThreadLocalRequest()!=null)
		
		if(RequestFactoryServlet.getThreadLocalRequest()!=null){
			
			Object userObj = RequestFactoryServlet.getThreadLocalRequest().getSession().getAttribute("user");
			if(userObj!=null){
				User2 user = (User2)userObj;
				logger.info(String.format("Found user '%s' in the session, user-id: %s", user.getEmail(), user.getId()));
				return user;
				
			}
		}
		
		logger.info("No user found in the session");
		
		return null;
	}
	public boolean isAdmin() {
		return isAdmin;
	}
	public void setAdmin(boolean isAdmin) {
		this.isAdmin = isAdmin;
	}
	
	@Override
	public String toString() {
		return String.format("E-mail: %s, id: %s", getEmail(), getId());
	}
	public Long getKarma() {
		return karma;
	}
	public void setKarma(Long karma) {
		this.karma = karma;
	}
	
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = super.hashCode();
		result = prime * result + ((email == null) ? 0 : email.hashCode());
		return result;
	}
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (getClass() != obj.getClass())
			return false;
		User2 other = (User2) obj;
		if (email == null) {
			if (other.email != null)
				return false;
		} else if (!email.equals(other.email))
			return false;
		return true;
	}
	
	/**
	 * Increase/Decrease Karma and persist user
	 */
	public void changeKarma(long karma){
		if(this.karma == null){
			this.karma = karma;
		}else{
			this.karma = this.karma + karma;
		}
		
		persist();
	}
	public Double getBalanceBitcoin() {
		if(balanceBitcoin==null) return 0d;
		return balanceBitcoin;
	}
	public void setBalanceBitcoin(Double balanceBitcoin) {
		this.balanceBitcoin = balanceBitcoin;
	}
	
	public boolean decreaseBalanceBitcoin(Double amount){
		
		if(getBalanceBitcoin()<amount){
			return false;
		}
		
		setBalanceBitcoin(getBalanceBitcoin()-amount);
		
		try{
			persist();
		}catch(Exception e){
			logger.error("Could not save user after decreasing balance", e);
			return false;
		}
		
		return true;
	}
	public Double getBalanceEuro() {
		
		if(balanceEuro==null) return 0d;
		return balanceEuro;
	}
	public void setBalanceEuro(Double balanceEuro) {
		this.balanceEuro = balanceEuro;
	}
	public String getBankRecipient() {
		return bankRecipient;
	}
	public void setBankRecipient(String bankRecipient) {
		this.bankRecipient = bankRecipient;
	}
	public String getBankIBAN() {
		return bankIBAN;
	}
	public void setBankIBAN(String bankIBAN) {
		this.bankIBAN = bankIBAN;
	}
	public String getBankBIC() {
		return bankBIC;
	}
	public void setBankBIC(String bankBIC) {
		this.bankBIC = bankBIC;
	}
	public String getBankName() {
		return bankName;
	}
	public void setBankName(String bankName) {
		this.bankName = bankName;
	}
	public String getPass1() {
		return pass1;
	}
	public void setPass1(String pass1) {
		this.pass1 = pass1;
	}
	public Double getBalanceBitcoinReserved() {
		return balanceBitcoinReserved;
	}
	public void setBalanceBitcoinReserved(Double balanceBitcoinReserved) {
		this.balanceBitcoinReserved = balanceBitcoinReserved;
	}
	
	
	public Long persistNewUser(){
		
		String activationCode = AppUtilsShared.generateRandomNumber();
		
		
		Queue queue = QueueFactory.getDefaultQueue();
		
		queue.add(TaskOptions.Builder.withUrl("/mailWorker").
				param("recipient", getEmail()).
				param("text", "Willkommen bei "+AppUtilsShared.appTitle+"!"
				+ "\r\nUm deine Registrierung abzuschließen klicke bitte auf den folgenden Link: "
				+ "\r\n"+ActivationServlet.generateActivationLink(getEmail(), activationCode)).
				param("subject", "Dein Account auf "+AppUtilsShared.appTitle));
		
		setActivationCode(activationCode);
		setPass1(hashPassword(getPass1()));
		
		return persist();
	}
	protected static String hashPassword(String password) {
		MessageDigest digest;
		byte[] hash = null;
		try {
			digest = MessageDigest.getInstance("SHA-256");
			hash = digest.digest((password+de.gwtprime.server.util.AppUtils.hashSalt).getBytes("UTF-8"));
		} catch (NoSuchAlgorithmException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (UnsupportedEncodingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		return new String(hash);
	}
	
	
	public static User2 login(String userName, String password) throws Exception{

		User2 user = HibernateHelper.queryDB(new User2(), String.format("from User2 where email='%s' and pass1 = '%s'", userName, hashPassword(password)));
		
		if(user!=null && !user.isActive()){
			throw new Exception("Invalid login");
		}
		
		return user;
	}
	public String getActivationCode() {
		return activationCode;
	}
	public void setActivationCode(String activationCode) {
		this.activationCode = activationCode;
	}
	public boolean isActive() {
		return isActive;
	}
	public void setActive(boolean isActive) {
		this.isActive = isActive;
	}
	
	@OneToMany(mappedBy = "user", cascade = CascadeType.REMOVE)
	public Set<Transaction> getTransactions() {
		return transactions;
	}
	public void setTransactions(Set<Transaction> transactions) {
		this.transactions = transactions;
	}
	
	public void addTransaction(Transaction transaction){
		EntityManager em = HibernateHelper.beginTransactionWithoutTransaction();

		try{
			transaction.setUser(this);
			getTransactions().add(transaction);
			
			persist();
			transaction.persist();
		}finally{
			em.close();
		}
	}
	
	public Boolean containsTxd(Transaction trx){
		String query = String.format("from %s where transactionId='%s'", Transaction.class.getName(), trx.getTransactionId());

		Transaction matchingTrx = (Transaction)HibernateHelper.queryDB(new Transaction(), query);
		return matchingTrx!=null;
	}
	public String getPrivateWithdrawAddress() {
		return privateWithdrawAddress;
	}
	public void setPrivateWithdrawAddress(String privateWithdrawAddress) {
		this.privateWithdrawAddress = privateWithdrawAddress;
	}
}